﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using Language.Utility;
using Microsoft.VisualStudio.Language.Intellisense;
using Microsoft.VisualStudio.Text;
using Microsoft.VisualStudio.Text.Editor;

namespace Language.Intellisense.Signature
{
    /// <summary>
    /// This is a helper class that helps encapsulate a particular session with it's associated signatures
    /// </summary>
    internal class HelpSession
    {
        #region Public Properties

        public ISignatureHelpSession Session { get; private set; }
        public ITrackingPoint TriggerPoint { get; private set; }
        public IList<Signature> Signatures { get; private set; } = new List<Signature>();

        public ITextView TextView => Session.TextView;
        public ITextSnapshot Snapshot => TextView.TextBuffer.CurrentSnapshot;

        public Signature SelectedSignature { get; private set; }

        #endregion Public Properties

        #region C-Tor

        public HelpSession( ISignatureHelpSession session )
        {
            Session = session;

            TriggerPoint = session.GetTriggerPoint( session.TextView.TextBuffer );

            session.Recalculated += Session_Recalculated;
            session.TextView.Caret.PositionChanged += Caret_PositionChanged;
            session.TextView.TextBuffer.Changed += TextBuffer_Changed;
            session.Dismissed += Session_Dismissed;
        }

        #endregion C-Tor

        #region Public Methods

        public void Clear()
        {
            foreach ( Signature signature in Signatures )
            {
                signature.Dispose();
            }

            Signatures.Clear();
        }

        public void RefreshSelectedSignature()
        {
            Debug.Print( $"Previously selected signature: {((ISignature)SelectedSignature)?.Content}" );

            if ( SelectedSignature != null )
            {
                foreach ( Signature signature in Signatures )
                {
                    if ( signature.Overload.Is( SelectedSignature.Overload ) )
                    {
                        Debug.Print( $"Setting signature to: {( (ISignature)signature ).Content}" );

                        Session.SelectedSignature = signature;
                        break;
                    }
                }
            }
        }

        public void Recalculate()
        {
            Session.SelectedSignatureChanged -= Session_SelectedSignatureChanged;
            Session.Recalculate();
        }

        public void Dismiss()
        {
            Session.Dismiss();
        }

        #endregion Public Methods

        #region Private Methods

        private bool MoveTriggerPointToCaret( CaretPosition position )
        {
            SnapshotPoint? point = position.Point.GetPoint( Session.TextView.TextBuffer, PositionAffinity.Successor );

            if ( point != null )
            {
                TriggerPoint = Session.TextView.TextBuffer.CurrentSnapshot.CreateTrackingPoint( point.Value.Position, PointTrackingMode.Negative );

                return true;
            }

            return false;
        }

        #endregion Private Methods

        #region Event Handlers

        private void Session_Recalculated( object sender, EventArgs e )
        {
            Session.SelectedSignatureChanged += Session_SelectedSignatureChanged;
            RefreshSelectedSignature();
        }

        private void Session_SelectedSignatureChanged( object sender, SelectedSignatureChangedEventArgs e )
        {
            Debug.Print( $"Selected signature set to: {e.NewSelectedSignature.Content}" );

            SelectedSignature = e.NewSelectedSignature as Signature;
        }

        private void Caret_PositionChanged( object sender, CaretPositionChangedEventArgs e )
        {
            Debug.Assert( sender.Equals( Session.TextView.Caret ) );

            if ( MoveTriggerPointToCaret( e.NewPosition ) )
            {
                CaretPositionChanged?.Invoke( this, e );
            }
        }

        private void TextBuffer_Changed( object sender, TextContentChangedEventArgs e )
        {
            Debug.Assert( sender.Equals( Session.TextView.TextBuffer ) );

            MoveTriggerPointToCaret( Session.TextView.Caret.Position );

            BufferChanged?.Invoke( this, e );
        }

        private void Session_Dismissed( object sender, EventArgs e )
        {
            Clear();

            Dismissed?.Invoke( this );


            Session.Recalculated -= Session_Recalculated;
            Session.SelectedSignatureChanged -= Session_SelectedSignatureChanged;
            Session.TextView.Caret.PositionChanged -= Caret_PositionChanged;
            Session.TextView.TextBuffer.Changed -= TextBuffer_Changed;
            Session.Dismissed -= Session_Dismissed;
        }

        #endregion Event Handlers

        #region Events

        public delegate void CaretPositionChangedHandler( HelpSession session, CaretPositionChangedEventArgs e );
        public delegate void BufferChangedHandler( HelpSession session, TextContentChangedEventArgs e );
        public delegate void DismissedHandler( HelpSession session );

        public event CaretPositionChangedHandler CaretPositionChanged;
        public event BufferChangedHandler BufferChanged;
        public event DismissedHandler Dismissed;

        #endregion Events
    }

    /// <summary>
    /// Creates and maintains a list of signatures for a particular signature help session
    /// </summary>
    /// <remarks>
    /// There will only be one of these per ITextBuffer, so a single instance of this class will be reused by every session for a particular file
    /// </remarks>
    internal class HelpSource : ISignatureHelpSource
    {
        #region Private Data

        private Stub.Database _database;
        private List<HelpSession> _sessions = new List<HelpSession>();

        #endregion Private Data

        #region C-Tor

        public HelpSource( Stub.Database database )
        {
            _database = database;
        }

        #endregion C-Tor

        #region Private Methods

        [Conditional( "DEBUG" )]
        private void PrintSignatures( List<Parser.Function> functions, SnapshotPoint point )
        {
            Debug.Print( $"Evaluating functions for position: {point}" );
            Debug.Print( $"Help session has {functions.Count} potential functions for signatures:" );

            foreach( Parser.Function function in functions )
            {
                if( function.Resolved != null )
                {
                    Debug.Print( $"  - {function.Resolved.Name} ({function.Start.Start}-{function.End.End})" );

                    IEnumerable<Stub.Function> overloads = function.Resolved.GetOverloads();

                    foreach ( Stub.Function overload in overloads )
                    {
                        Debug.Print( $"    - Overload with {overload.Parameters.Count} parameters" );
                    }
                }
            }
        }

        [Conditional("DEBUG")]
        private void PrintSignatures( IList<ISignature> signatures )
        {
            Debug.Print( $"Help session has {signatures.Count} signatures:" );
            foreach( ISignature signature in signatures )
            {
                Debug.Print( $" - {signature.Content}" );
            }
        }

        private void CreateSignatures( List<Parser.Function> functions, HelpSession session, IList<ISignature> signatures )
        {
            SnapshotPoint point = session.TriggerPoint.GetPoint( session.Snapshot );

            foreach ( var function in functions )
            {
                if ( function.Resolved != null )
                {
                    if ( function.Start.Start < point.Position && function.End.End >= point.Position )
                    {
                        ITrackingSpan applicableToSpan = session.Snapshot.CreateTrackingSpan( function.Start.Start, function.End.End - function.Start.Start, SpanTrackingMode.EdgeInclusive );

                        IEnumerable<Stub.Function> overloads = function.Resolved.GetOverloads();

                        foreach ( Stub.Function overload in overloads )
                        {
                            Signature signature = new Signature( function, overload, session.TextView, applicableToSpan );
                            session.Signatures.Add( signature );
                            signatures.Add( signature );
                        }
                    }
                }
            }

            PrintSignatures( functions, point );
            PrintSignatures( signatures );

            if ( signatures.Count == 0 )
            {
                session.Dismiss();
            }
        }

        #endregion Private Methods

        #region Event Handlers

        private void HelpSession_CaretPositionChanged( HelpSession session, CaretPositionChangedEventArgs e )
        {
            session.Recalculate();
        }

        private void HelpSession_BufferChanged( HelpSession session, TextContentChangedEventArgs e )
        {
            session.Recalculate();
        }

        private void HelpSession_Dismissed( HelpSession session )
        {
            session.CaretPositionChanged -= HelpSession_CaretPositionChanged;
            session.BufferChanged -= HelpSession_BufferChanged;
            session.Dismissed -= HelpSession_Dismissed;

            _sessions.Remove( session );
        }

        #endregion Event Handlers

        #region ISignatureHelpSource

        void ISignatureHelpSource.AugmentSignatureHelpSession( ISignatureHelpSession session, IList<ISignature> signatures )
        {
            HelpSession helpSession = _sessions.Find( s => s.Session == session );

            if ( helpSession == null )
            {
                helpSession = new HelpSession( session );
                _sessions.Add( helpSession );

                helpSession.CaretPositionChanged += HelpSession_CaretPositionChanged;
                helpSession.BufferChanged += HelpSession_BufferChanged;
                helpSession.Dismissed += HelpSession_Dismissed;

                var functions = ParseLine( helpSession.TriggerPoint, helpSession.Snapshot, _database );
                CreateSignatures( functions, helpSession, signatures );
            }
            else
            {
                // We're "recalculating", so remove the old signatures (and make sure they can be garbage collected)
                helpSession.Clear();

                var functions = ParseLine( helpSession.TriggerPoint, helpSession.Snapshot, _database );
                CreateSignatures( functions, helpSession, signatures );
            }
        }

        private static List<Parser.Function> ParseLine( ITrackingPoint triggerPoint, ITextSnapshot snapshot, Stub.Database database )
        {
            SnapshotPoint point = triggerPoint.GetPoint( snapshot );

            FilePath path = new FilePath() { Absolute = snapshot.TextBuffer.ExtractFilename() };
            Parser parser = new Parser( database, path.Relative );

            var functions = parser.ResolveFunctions( point );

            // Don't display function signature help for declarations
            if ( parser.IsFunctionDeclaration )
                return new List<Parser.Function>();

            return functions;
        }

        ISignature ISignatureHelpSource.GetBestMatch( ISignatureHelpSession session )
        {
            throw new NotImplementedException();
        }

        #endregion ISignatureHelpSource

        #region IDisposable

        private bool disposedValue = false; // To detect redundant calls

        protected virtual void Dispose( bool disposing )
        {
            if ( !disposedValue )
            {
                if ( disposing )
                {
                    // TODO: dispose managed state (managed objects).
                }

                // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
                // TODO: set large fields to null.

                disposedValue = true;
            }
        }

        // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.
        // ~HelpSource() {
        //   // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
        //   Dispose(false);
        // }

        // This code added to correctly implement the disposable pattern.
        void IDisposable.Dispose()
        {
            // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
            Dispose( true );
            // TODO: uncomment the following line if the finalizer is overridden above.
            // GC.SuppressFinalize(this);
        }

        #endregion IDisposable
    }
}
